/**
 * 
 */
package edu.sjtu.se.prophet.driver;

import java.awt.Dimension;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import javax.imageio.ImageIO;

import org.apache.log4j.Logger;

import edu.sjtu.se.prophet.exception.EdgeExistsException;
import edu.sjtu.se.prophet.exception.NodeExistsException;
import edu.sjtu.se.prophet.exception.NodeNotFoundException;
import edu.sjtu.se.prophet.graph.Edge;
import edu.sjtu.se.prophet.graph.Graph;
import edu.sjtu.se.prophet.graph.Node;
import edu.uci.ics.jung.algorithms.layout.CircleLayout;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.SparseMultigraph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.visualization.BasicVisualizationServer;
import edu.uci.ics.jung.visualization.VisualizationImageServer;

/**
 * @author willard
 * 
 */
public class GenerateGraphImageDriver extends DefaultPropertiesLoader {

    private static Logger logger = Logger.getLogger(GenerateGraphImageDriver.class);

    public static final String ARFF_FILE = "data.arff.file";
    public static final String IMAGE_FILE = "data.image.file";
    public static final String START_RED = "out.start.red";
    public static final String START_GREEN = "out.start.green";
    public static final String START_BLUE = "out.start.blue";
    public static final String END_RED = "out.end.red";
    public static final String END_GREEN = "out.end.green";
    public static final String END_BLUE = "out.end.blue";

    private static final String LEVEL = "Level";

    private float startRed = 1;
    private float startGreen = 1;
    private float startBlue = 1;
    private float endRed = 0;
    private float endGreen = 0;
    private float endBlue = 0;
    private String arffFileName = "data.arff";
    private String imageFileName = "data.png";


    /**
     * @param args
     * @throws IOException
     * @throws FileNotFoundException
     */
    public static void main(String[] args) throws FileNotFoundException, IOException {
        GenerateGraphImageDriver driver = new GenerateGraphImageDriver();
        driver.load(args);
        driver.generate();
    }


    private void generate() throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(arffFileName));
        String line = null;
        Graph graph = new Graph();
        while ((line = br.readLine()) != null) {
            if (line.startsWith("@"))
                continue;
            String[] parts = line.split("%");
            if (parts.length < 2)
                continue;
            String[] metrics = parts[0].split(",");
            int level = Integer.parseInt(metrics[metrics.length - 1].replace("Level", "").replace(" ", ""));
            String[] functions = parts[1].replace(", ", ",").trim().split(" ");
            for (String func : functions) {
                if ("".equals(func.trim())) {
                    logger.warn("Empty function name found!");
                }
                Node node = graph.findNode(func);
                if (node != null) {
                    Integer l = (Integer) node.getMetrics().get(LEVEL);
                    node.getMetrics().put(LEVEL, l + level);
                } else {
                    try {
                        graph.addNode(func);
                        graph.findNode(func).getMetrics().put(LEVEL, level);
                    } catch (NodeExistsException e) {
                        logger.error("Not expected when adding node!", e);
                    }
                }
            }

            for (int i = 1; i < functions.length; i++) {
                try {
                    graph.addEdge(functions[i - 1], functions[i]);
                } catch (NodeNotFoundException e) {
                    logger.error("Not expected when adding edge!", e);
                } catch (EdgeExistsException e) {
                    // Ignore
                }
            }
        }
        br.close();

        render(graph);
    }


    private void render(Graph graph) {
        edu.uci.ics.jung.graph.Graph<Node, Edge> jungGraph = new SparseMultigraph<Node, Edge>();

        float maxLevel = 0.1f;
        for (Node node : graph.getNodes()) {
            jungGraph.addVertex(node);
            int level = (Integer) node.getMetrics().get(LEVEL);
            if (level > maxLevel)
                maxLevel = level;
        }

        for (Edge edge : graph.getEdges()) {
            jungGraph.addEdge(edge, edge.getStart(), edge.getEnd(), EdgeType.DIRECTED);
        }

        Layout<Node, Edge> layout = new CircleLayout(jungGraph);
        BasicVisualizationServer<Node, Edge> vv =
                new BasicVisualizationServer<Node, Edge>(layout);
        VisualizationImageServer<Node, Edge> vis =
                new VisualizationImageServer<Node, Edge>(vv.getGraphLayout(),
                        vv.getGraphLayout().getSize());

        // Create the buffered image
        BufferedImage image = (BufferedImage) vis.getImage(
                new Point2D.Double(vv.getGraphLayout().getSize().getWidth() / 2,
                        vv.getGraphLayout().getSize().getHeight() / 2),
                new Dimension(vv.getGraphLayout().getSize()));

        // Write image to a png file
        File outputfile = new File(imageFileName);

        try {
            ImageIO.write(image, "png", outputfile);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }

    }


    private void write(Graph graph) throws IOException {
        float maxLevel = 0.1f;
        for (Node node : graph.getNodes()) {
            int level = (Integer) node.getMetrics().get(LEVEL);
            if (level > maxLevel)
                maxLevel = level;
        }
        BufferedWriter bw = new BufferedWriter(new FileWriter(imageFileName));
        bw.write("digraph G {\n");
        for (Node node : graph.getNodes()) {
            int level = (Integer) node.getMetrics().get(LEVEL);
            float ratio = ((float) level) / maxLevel;
            float red = getColor(startRed, endRed, ratio);
            float green = getColor(startGreen, endGreen, ratio);
            float blue = getColor(startBlue, endBlue, ratio);
            bw.write("\"" + node.getId() + "\" [style=filled,color=\"" + red + " " + green + " " + blue + "\"];\n");
        }

        for (Edge edge : graph.getEdges()) {
            bw.write("\"" + edge.getStart().getId() + "\" -> \"" + edge.getEnd().getId() + "\"\n");
        }
        bw.write("}\n");
        bw.close();
    }


    private float getColor(float start, float end, float ratio) {
        if (start < 0)
            start = 0;
        if (end > 1)
            end = 1;
        if (ratio > 1)
            ratio = 1;
        if (ratio < 0)
            ratio = 0;
        return start + (end - start) * ratio;
    }


    public void load(String[] args) throws FileNotFoundException, IOException {
        super.load(args);

        if (properties.containsKey(START_RED)) {
            startRed = Float.parseFloat(properties.getProperty(START_RED));
        }
        if (properties.containsKey(START_GREEN)) {
            startGreen = Float.parseFloat(properties.getProperty(START_GREEN));
        }
        if (properties.containsKey(START_BLUE)) {
            startBlue = Float.parseFloat(properties.getProperty(START_BLUE));
        }
        if (properties.containsKey(END_RED)) {
            endRed = Float.parseFloat(properties.getProperty(END_RED));
        }
        if (properties.containsKey(END_GREEN)) {
            endGreen = Float.parseFloat(properties.getProperty(END_GREEN));
        }
        if (properties.containsKey(END_BLUE)) {
            endBlue = Float.parseFloat(properties.getProperty(END_BLUE));
        }
        logger.info("Color bounds: (" + startRed + ", " + startGreen + ", " + startBlue + ") - (" + endRed + ", "
                + endGreen + ", " + endBlue + ")");

        arffFileName = properties.getProperty(ARFF_FILE);
        logger.info("ARFF data file: " + arffFileName);

        imageFileName = properties.getProperty(IMAGE_FILE);
        logger.info("DOT data file: " + imageFileName);
    }
}
